import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;

import org.json.JSONObject;

/*
 * Слова в кодировке win1251. Символы хранятся в байтах
 * Выбрана однобайтная кодировка для уменьшения потребности в ОЗУ.
 * Если слово не может без потерь преобразоваться в win1251, то слово остается в WordUnicode
 */
class Word extends WordInterface {

    final byte[] value;

    Word(final String w) {
        value = w.getBytes(Charset.forName("Cp1251"));
    }

    Word(final WordUnicode w) {
        String s = w.toString();
        value = s.getBytes(Charset.forName("Cp1251"));
    }

    Word(final byte[] v) {
        value = v;
    }

    /*
     * Преобразовать слово из 1251 в String
     */
    public String toString() {
        return new String(value, Charset.forName("Cp1251"));
    }

    /*
     * Возвращает число символов в слове
     */
    final public int length() {
        return value.length;
    }

    /*
     * Сравнение для отношения лексиграфического порядка.
     * Сравнение слов в win1251. С учетом регистра
     * Метод используется для сравнения ключей деревьев dict и data, которые всегда в 1251.
     * 
     * Также используется в сортировке массивов, поэтому важно, чтобы порядок был
     * именно лексиграфический (чтобы массивы были отсортированы по размеру строк)
     */
    public int compareTo(WordInterface b) {
        if (b instanceof Space) {
            return 1;
        }
        if (b instanceof WordUnicode) {
            b = new Word((WordUnicode) b);
        }
        final int length = value.length;
        final byte[] y = ((Word) b).value;
        int cmp;
        if ((cmp = Integer.compare(length, y.length)) != 0) {
            return cmp;
        }
        for (int i = 0; i < length; i++) {
            if ((cmp = Byte.compare(value[i], y[i])) != 0) {
                return cmp;
            }
        }
        return 0;
    }

    //
    // Начало. Набор функций для алгоритма Бойера—Мура
    //

    /*
     * Взять подстроку из строки text начиная с символа start и завершая символом
     * end (символ на позиции end не включается)
     * 
     * @text - исходная строка
     * 
     * @start - стартовый символ в строке text
     * 
     * @end - завершающий символ в строке text, символ на позиции end не включается
     * в подстроку
     */
    static public byte[] substring(final byte[] text, final int start, final int end) {
        final byte[] ss = new byte[end - start];
        int j = 0;
        for (int i = start; i < end; i++)
            ss[j++] = text[i];
        return ss;
    }

    /*
     * Сравнивает 2 символа в win1251 без учета регистра
     */
    static boolean equalsNoCase(byte v1, byte v2) {
        // переводим в нижний регистр
        if (v1 >= (byte) 0xC0 && v1 <= (byte) 0xDF || v1 >= (byte) 0x41 && v1 <= (byte) 0x5A)
            v1 += (byte) 0x20;
        if (v2 >= (byte) 0xC0 && v2 <= (byte) 0xDF || v2 >= (byte) 0x41 && v2 <= (byte) 0x5A)
            v2 += (byte) 0x20;

        return v1 == v2;
    }

    /*
     * Переводит символ в win1251 в противоположный регистр для кириллических и
     * латинских символов
     */
    static int reverseCase(byte v) {
        if (v >= (byte) 0xC0 && v <= (byte) 0xDF) // переводим кириллицу в нижний регистр
            v += (byte) 0x20;
        else if (v >= (byte) 0xE0 && v <= (byte) 0xFF) // переводим кириллицу в верхний регистр
            v -= (byte) 0x20;
        else if (v >= (byte) 0x41 && v <= (byte) 0x5A) // переводим латиницу в нижний регистр
            v += (byte) 0x20;
        else if (v >= (byte) 0x61 && v <= (byte) 0x7A) // переводим латиницу в верхний регистр
            v -= (byte) 0x20;
        return v;
    }

    /*
     * Сравнение 2-х предложений в win1251 без учета регистра
     * Используется в поиске вхождений подстроки в строку (AVL.indexOf)
     */
    static boolean equalsNoCase(final byte[] v1, final byte[] v2) {
        for (int i = 0; i < v1.length; i++) {
            final byte b1 = v1[i];
            final byte b2 = v2[i];
            if (b1 != b2 && !equalsNoCase(b1, b2)) {
                return false;
            }
        }
        return true;
    }

    //
    // Конец. Набор функций для алгоритма Бойера—Мура
    //

    /*
     * Строку разбивает на слова. Все слова - WordUnicode
     * Конвертация слов в Word (win1251) для слов из поля value справочников ФНСИ
     * проивзодится в методе load
     */
    public static ArrayList<WordInterface> split(final String str) {
        ArrayList<WordInterface> arr = new ArrayList<WordInterface>();
        if (str == null || str.length() == 0)
            return arr;

        StringBuilder sb = new StringBuilder();
        char last = str.charAt(0);
        byte spaces = 0;
        if (last == ' ')
            spaces = 1;
        else
            sb.append(last);
        for (int i = 1; i < str.length(); i++) {
            char cur = str.charAt(i);
            if (Character.isLetterOrDigit(last) && Character.isLetterOrDigit(cur)) {
                sb.append(cur);
            } else if (last == ' ' && cur == ' ') {
                spaces++;
            } else if (last != ' ' && cur == ' ') {
                spaces = 1;
                arr.add(new WordUnicode(sb.toString()));
                sb.setLength(0);
            } else if (last == ' ' && cur != ' ') {
                arr.add(new Space(spaces));
                spaces = 0;
                sb.append(cur);
            } else {
                arr.add(new WordUnicode(sb.toString()));
                sb.setLength(0);
                sb.append(cur);
            }
            last = cur;
        }
        if (spaces != 0)
            arr.add(new Space(spaces));
        else
            arr.add(new WordUnicode(sb.toString()));

        return arr;
    }

    /*
     * Строку в win1251 преобразовать в String
     */
    static public String toString(final byte[] value) {
        return new String(value, Charset.forName("Cp1251"));
    }

    JSONObject toJSON() {
        JSONObject jo = new JSONObject();
        jo.put("W", toString());
        return jo;
    }

    void write(DataOutputStream dos) throws IOException {
        dos.writeByte(0x57); // W
        TreeBinaryReaderWriter.writeArray(dos, value);
    }
}